<template>
  <div class="game-2048">
    <div class="header">
      <h2>2048 数字拼图</h2>
      <div class="stats">
        <div class="score">分数：{{ score }}</div>
        <div class="best">最高：{{ best }}</div>
      </div>
      <div class="actions">
        <button class="btn" @click="newGame">新开一局</button>
        <button class="btn" @click="toggleHelp">玩法说明</button>
      </div>
    </div>

    <div class="board" tabindex="0" ref="boardEl" @keydown.prevent="onKey">
      <div v-for="(row, y) in board" :key="'row-'+y" class="row">
        <div v-for="(cell, x) in row" :key="'cell-'+x" class="cell">
          <div v-if="cell" :class="tileClass(cell)">{{ cell }}</div>
        </div>
      </div>
      <div v-if="gameWon" class="overlay win">
        <div class="msg">你合成了 2048！继续挑战？</div>
        <button class="btn" @click="continuePlay">继续</button>
      </div>
      <div v-if="gameOver && !gameWon" class="overlay over">
        <div class="msg">没有可移动步了</div>
        <button class="btn" @click="newGame">再来一局</button>
      </div>
    </div>

    <div v-if="showHelp" class="help">
      - 使用方向键或 WASD 移动方块。
      - 相同数字的方块相撞会合成为更大的数字。
      - 每次移动后会随机生成一个 2 或 4。
      - 目标是合成到 2048！
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'

const size = 4
const board = ref(Array.from({ length: size }, () => Array(size).fill(0)))
const score = ref(0)
const best = ref(parseInt(localStorage.getItem('game2048Best') || '0'))
const gameOver = ref(false)
const gameWon = ref(false)
const showHelp = ref(false)
const boardEl = ref(null)

function newGame() {
  score.value = 0
  gameOver.value = false
  gameWon.value = false
  for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) board.value[y][x] = 0
  addRandomTile()
  addRandomTile()
  focusBoard()
}

function continuePlay() {
  gameWon.value = false
  focusBoard()
}

function focusBoard() {
  nextTick(() => boardEl.value && boardEl.value.focus())
}

function addRandomTile() {
  const empties = []
  for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) if (!board.value[y][x]) empties.push([y, x])
  if (!empties.length) return
  const [y, x] = empties[Math.floor(Math.random() * empties.length)]
  board.value[y][x] = Math.random() < 0.9 ? 2 : 4
}

function slideAndMerge(line) {
  const nonZero = line.filter(v => v)
  const res = []
  let gained = 0
  for (let i = 0; i < nonZero.length; i++) {
    if (i < nonZero.length - 1 && nonZero[i] === nonZero[i + 1]) {
      const v = nonZero[i] * 2
      res.push(v)
      gained += v
      i++
    } else {
      res.push(nonZero[i])
    }
  }
  while (res.length < size) res.push(0)
  const moved = res.some((v, i) => v !== line[i])
  return { res, gained, moved }
}

function move(direction) {
  if (gameOver.value || (gameWon.value && direction !== 'continue')) return
  let changed = false
  let gainedTotal = 0

  if (direction === 'left' || direction === 'right') {
    for (let y = 0; y < size; y++) {
      const line = board.value[y].slice()
      const base = direction === 'left' ? line : line.slice().reverse()
      const { res, gained, moved } = slideAndMerge(base)
      const final = direction === 'left' ? res : res.slice().reverse()
      board.value[y] = final
      if (moved) { changed = true; gainedTotal += gained }
    }
  } else if (direction === 'up' || direction === 'down') {
    for (let x = 0; x < size; x++) {
      const line = []
      for (let y = 0; y < size; y++) line.push(board.value[y][x])
      const base = direction === 'up' ? line : line.slice().reverse()
      const { res, gained, moved } = slideAndMerge(base)
      const final = direction === 'up' ? res : res.slice().reverse()
      for (let y = 0; y < size; y++) board.value[y][x] = final[y]
      if (moved) { changed = true; gainedTotal += gained }
    }
  }

  if (changed) {
    score.value += gainedTotal
    if (score.value > best.value) {
      best.value = score.value
      localStorage.setItem('game2048Best', String(best.value))
    }
    for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) if (board.value[y][x] === 2048) gameWon.value = true
    addRandomTile()
    if (!canMove()) gameOver.value = true
  }
}

function canMove() {
  // any empty
  for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) if (!board.value[y][x]) return true
  // any mergeable neighbors
  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      const v = board.value[y][x]
      if (y + 1 < size && board.value[y + 1][x] === v) return true
      if (x + 1 < size && board.value[y][x + 1] === v) return true
    }
  }
  return false
}

function onKey(e) {
  const map = {
    ArrowLeft: 'left', ArrowRight: 'right', ArrowUp: 'up', ArrowDown: 'down',
    a: 'left', d: 'right', w: 'up', s: 'down', A: 'left', D: 'right', W: 'up', S: 'down'
  }
  const dir = map[e.key]
  if (dir) move(dir)
}

function toggleHelp() { showHelp.value = !showHelp.value }

onMounted(() => {
  newGame()
  window.addEventListener('keydown', onKey)
})

onBeforeUnmount(() => {
  window.removeEventListener('keydown', onKey)
})

function tileClass(v) {
  const cls = ['tile']
  cls.push('t-' + v)
  return cls.join(' ')
}
</script>

<style scoped>
.game-2048 { max-width: 420px; margin: 16px auto; font-family: system-ui, -apple-system, Segoe UI, PingFang SC, Microsoft YaHei; }
.header { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.stats { display:flex; gap:12px; }
.score, .best { background:#bbada0; color:#fff; padding:6px 10px; border-radius:6px; font-weight:600; }
.actions { display:flex; gap:8px; }
.btn { padding:6px 10px; border:none; border-radius:6px; background:#8f7a66; color:#fff; cursor:pointer; }
.btn:hover { filter:brightness(1.05); }
.board { margin-top:12px; outline:none; background:#bbada0; padding:10px; border-radius:8px; display:grid; grid-template-rows: repeat(4, 1fr); gap:10px; }
.row { display:grid; grid-template-columns: repeat(4, 1fr); gap:10px; }
.cell { background:#cdc1b4; border-radius:8px; position:relative; height:80px; display:flex; align-items:center; justify-content:center; }
.tile { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; border-radius:8px; font-weight:700; font-size:22px; transition: transform .08s ease; }
/* 简化的颜色方案 */
.t-2    { background:#eee4da; color:#776e65; }
.t-4    { background:#ede0c8; color:#776e65; }
.t-8    { background:#f2b179; color:#f9f6f2; }
.t-16   { background:#f59563; color:#f9f6f2; }
.t-32   { background:#f67c5f; color:#f9f6f2; }
.t-64   { background:#f65e3b; color:#f9f6f2; }
.t-128  { background:#edcf72; color:#f9f6f2; font-size:20px; }
.t-256  { background:#edcc61; color:#f9f6f2; font-size:20px; }
.t-512  { background:#edc850; color:#f9f6f2; font-size:20px; }
.t-1024 { background:#edc53f; color:#f9f6f2; font-size:18px; }
.t-2048 { background:#edc22e; color:#f9f6f2; font-size:18px; }
.overlay { position:absolute; inset:10px; border-radius:8px; background:rgba(238,228,218,.85); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; text-align:center; }
.help { margin-top:10px; background:#f8f6f2; border:1px solid #eee; border-radius:8px; padding:8px 10px; color:#776e65; font-size:14px; }
</style>